Executive Summary
How to implement email magic links with minimal attack surface and a clean UX that users actually trust.
Magic links are great… until they aren’t. The security model is simple: a link is a bearer token. Treat it like one.
Short TTLs, single-use tokens, device binding (optional), and clear recovery paths turn passwordless into trustworthy.
“Production is where good ideas meet boring reality. The winners instrument the boring part.”AI & Dev Dispatch
The Core Idea
Most “AI failures” are system failures: missing contracts, missing logs, missing ownership lines. Fix the system, and the model suddenly looks smarter.
Contract
Define the stable input/output boundary first.
Logs
Capture raw facts, not just summaries.
Policy
Centralize allow/deny decisions and expose reason codes.
UX
Make failure legible and recoverable.
// Magic link tokens: store only hashes.
// token_raw is shown ONCE in email.
// token_hash is stored in DB and compared with a constant-time check.
const tokenHash = await sha256(tokenRaw);
await db.insert({ token_hash: tokenHash, expires_at: nowPlusMinutes(10), used_at: null });
That snippet is not a complete app. It’s a reminder: your system should prefer verifiable facts over narrative.
Failure Modes You’ll Actually See
-
Long-lived links
A link forwarded to the wrong person becomes an account takeover.
-
Reuse
Magic links must be single-use; otherwise they become permanent passwords.
-
Weak rate limits
Attackers can spam login links and train users to click garbage.
-
No recovery
Users need a clean ‘change email / regain access’ path that doesn’t require admin snooping.
Implementation Notes
Magic link tokens should be random, hashed at rest, and expire fast (5–15 minutes).
Bind tokens to a session nonce or device fingerprint if your threat model needs it.
Rate-limit by IP + email + device, and provide a safe resend flow.
For architecture and rollout planning, use the Contact Hub.
Ship‑Ready Checklist
Use this as a pre‑deploy gate. If you can’t check these boxes, don’t pretend you’re “done.”
- A single source of truth for versions (prompt/policy/schema) and a way to display them in-app.
- Request correlation ID visible in UI, logged server-side, and searchable.
- Explicit failure UX: what happened, why, and a safe next step.
- An audit trail you can replay: inputs, decisions, outputs, and cost facts.
- A small test harness (even 20 cases) that runs before deployment.
Further Reading
External references (full links):
Related Reads in This Series
Want this turned into a working product?
Use the Contact Hub to scope features, security, billing, and the deployment plan.